<script setup>
import { ref, watch, nextTick, computed, onMounted, onBeforeUnmount, readonly } from "vue";
// import { chat } from "../tauriApi";
import { useI18n } from 'vue-i18n';
import { useAiToolsStore, orchestrateToolCalls } from '../stores/aiTools';
import { getAppSetting, setAppSetting } from '../tauriApi';
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
import { uid } from '../utils/uid';

// 调优：虚拟滚动参数（复杂消息场景）
const vscrollMinItemSize = 54; // 聊天气泡最小高度（含内边距与头像对齐）
const vscrollBuffer = 600; // 提前渲染上下缓冲区，避免高内容项临界抖动
const vscrollPrerender = 10; // 首屏预渲染若干项，减少首屏测量闪动
// 持久化写入节流/防抖时长
const MSG_PERSIST_DEBOUNCE_MS = 400;
const SCROLL_PERSIST_DEBOUNCE_MS = 300;

// 在顶层获取 t，供模板和逻辑使用
const { t } = useI18n();

const props = defineProps({
  modelKey: { type: String, required: false, default: "" },
  // 新增：独立会话标识，默认使用 'default'，与模型解耦合
  sessionId: { type: String, required: false, default: 'default' },
  // 新增：多轮工具调用配置
  multiRoundOptions: {
    type: Object,
    default: () => ({
      enabled: true,      // 是否启用多轮工具调用
      maxRounds: 100,     // 最大轮次
      concurrent: true,   // 是否并发执行工具
      showProgress: true  // 是否显示进度
    })
  }
});

const emit = defineEmits(['retract-to-input']);

const aiTools = useAiToolsStore();

const messages = ref([]);
// input removed; use external composer
const sending = ref(false);
const error = ref("");
const scrollRef = ref(null);
const currentTaskId = ref(null); // 用于取消当前请求
// 滚动状态（用于判断是否自动滚底）
const isAtBottom = ref(true);
// 复制提示状态
const copiedHintId = ref(null);
// per-message overrides removed; use model defaults

// 简易防抖：返回一个可取消的调度器
function makeDebounced(fn, wait) {
  let timer = null;
  const wrapper = (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      try { fn(...args); } catch {}
    }, wait);
  };
  wrapper.cancel = () => { if (timer) { clearTimeout(timer); timer = null; } };
  return wrapper;
}

// 新增：规范化助手内容，移除顶部空行并压缩多余空行
function sanitizeAssistant(input) {
  if (!input) return '';
  let s = String(input);
  // 去BOM、统一行尾
  s = s.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
  // 去除每行行尾多余空格
  s = s.split('\n').map(l => l.replace(/[\t ]+$/g, '')).join('\n');
  // 移除前导空行
  s = s.replace(/^\s*\n+/, '');
  // 压缩3个及以上连续空行为2个，保留段落感
  s = s.replace(/\n{3,}/g, '\n\n');
  // 末尾多余空白裁剪
  s = s.replace(/[\s\n]+$/, '');
  return s;
}

// 调优：在内容尺寸可能变化（如图片/代码折叠）时强制刷新测量
function forceVscrollUpdate() {
  nextTick(() => {
    try {
      const sc = scrollRef.value;
      if (sc && typeof sc.forceUpdate === 'function') sc.forceUpdate();
    } catch {}
  });
}

// 辅助：安全解析/美化 JSON 展示
function tryParseJSON(str) {
  if (typeof str !== 'string') return null;
  try { return JSON.parse(str); } catch { return null; }
}
function prettyJSON(input) {
  try {
    if (typeof input === 'string') {
      const parsed = tryParseJSON(input);
      return parsed ? JSON.stringify(parsed, null, 2) : input;
    }
    return JSON.stringify(input, null, 2);
  } catch {
    return String(input ?? '');
  }
}

// ============== 会话持久化（与模型解耦合，按会话ID存储） ==============
const persistKey = computed(() => props.sessionId ? `chat.session.${props.sessionId}` : null);
let saveTimer = null;
let scrollSaveTimer = null;

async function loadPersistedMessages() {
  if (!persistKey.value) {
    messages.value = [];
    return;
  }
  
  try {
    const raw = await getAppSetting(persistKey.value);
    // 新增：读取会话滚动恢复位置
    let savedIndex = -1;
    try {
      const savedRaw = await getAppSetting(`chat.scroll.${props.sessionId}`);
      if (savedRaw != null && savedRaw !== '') {
        const n = parseInt(String(savedRaw), 10);
        if (!isNaN(n)) savedIndex = n;
      }
    } catch {}
    if (raw) {
      try {
        const parsed = JSON.parse(raw);
        if (Array.isArray(parsed)) {
          // 加载时对历史助手消息做一次规范化，并补充稳定 id 与渲染tick
          messages.value = parsed.map(m => {
            const msg = (m && m.role === 'assistant') ? { ...m, content: sanitizeAssistant(m.content || '') } : { ...m };
            if (!msg.id) msg.id = uid('msg_');
            if (typeof msg.rt !== 'number') msg.rt = 0;
            return msg;
          });
          // 滚动到保存位置或底部
          nextTick(() => {
            const sc = scrollRef.value;
            const vmLen = (Array.isArray(visibleMessages?.value) ? visibleMessages.value.length : messages.value.length);
            const lastIndex = Math.max(0, vmLen - 1);
            const targetIndex = Math.max(0, Math.min(savedIndex, lastIndex));
            if (sc && typeof sc.scrollToItem === 'function' && savedIndex >= 0 && targetIndex <= lastIndex) {
              sc.scrollToItem(targetIndex);
              isAtBottom.value = targetIndex >= (lastIndex - 1);
            } else {
              const el = sc && sc.$el ? sc.$el : null;
              if (el) el.scrollTop = el.scrollHeight;
              isAtBottom.value = true;
            }
            forceVscrollUpdate();
          });
          return;
        }
      } catch {}
    }
    // 迁移兼容
    if (props.modelKey) {
      try {
        const legacyRaw = await getAppSetting(`chat.session.${props.modelKey}`);
        if (legacyRaw) {
          const legacyParsed = JSON.parse(legacyRaw);
          if (Array.isArray(legacyParsed)) {
            messages.value = legacyParsed.map(m => {
              const msg = (m && m.role === 'assistant') ? { ...m, content: sanitizeAssistant(m.content || '') } : { ...m };
              if (!msg.id) msg.id = uid('msg_');
              if (typeof msg.rt !== 'number') msg.rt = 0;
              return msg;
            });
            if (persistKey.value) {
              try { await setAppSetting(persistKey.value, JSON.stringify(messages.value)); } catch {}
            }
            nextTick(() => {
              const sc = scrollRef.value;
              const vmLen = (Array.isArray(visibleMessages?.value) ? visibleMessages.value.length : messages.value.length);
              const lastIndex = Math.max(0, vmLen - 1);
              const targetIndex = Math.max(0, Math.min(savedIndex, lastIndex));
              if (sc && typeof sc.scrollToItem === 'function' && savedIndex >= 0 && targetIndex <= lastIndex) {
                sc.scrollToItem(targetIndex);
                isAtBottom.value = targetIndex >= (lastIndex - 1);
              } else {
                const el = sc && sc.$el ? sc.$el : null;
                if (el) el.scrollTop = el.scrollHeight;
                isAtBottom.value = true;
              }
              forceVscrollUpdate();
            });
            return;
          }
        }
      } catch {}
    }
    messages.value = [];
  } catch {
    messages.value = [];
  }
}

function scheduleSaveMessages() {
  if (!persistKey.value) return;
  if (saveTimer) clearTimeout(saveTimer);
  saveTimer = setTimeout(async () => {
    try {
      await setAppSetting(persistKey.value, JSON.stringify(messages.value || []));
    } catch {}
  }, MSG_PERSIST_DEBOUNCE_MS);
}

function schedulePersistScroll(index) {
  if (!props.sessionId) return;
  const idx = Math.max(0, Number.isFinite(index) ? Math.floor(index) : 0);
  if (scrollSaveTimer) clearTimeout(scrollSaveTimer);
  scrollSaveTimer = setTimeout(async () => {
    try { await setAppSetting(`chat.scroll.${props.sessionId}`, String(idx)); } catch {}
  }, SCROLL_PERSIST_DEBOUNCE_MS);
}

onMounted(() => {
  loadPersistedMessages();
});

onBeforeUnmount(() => {
  if (saveTimer) clearTimeout(saveTimer);
  if (scrollSaveTimer) clearTimeout(scrollSaveTimer);
});

watch(messages, () => {
  scheduleSaveMessages();
}, { deep: true });

// 替换：会话切换时保存旧会话并加载新会话
watch(() => props.sessionId, async (now, old) => {
  if (old) {
    try {
      const oldKey = `chat.session.${old}`;
      await setAppSetting(oldKey, JSON.stringify(messages.value || []));
    } catch {}
  }
  // 切换会话时先清空当前消息，避免串台
  messages.value = [];
  // 再加载新会话
  await loadPersistedMessages();
});

// 计算运行中的聊天任务
const runningChatTasks = computed(() => {
  return aiTools.runningTasks.filter(task => task.toolId === 'chat.complete');
});

// 是否有正在运行的聊天任务
const hasRunningTask = computed(() => runningChatTasks.value.length > 0);

// 工具卡片 UI：图标映射与状态
function getToolIcon(name) {
  const id = String(name || '').toLowerCase();
  if (id.includes('applyedits')) return '🧩';
  if (id.includes('write_file')) return '📝';
  if (id.includes('create_directory')) return '📁';
  if (id.includes('move_file')) return '📦';
  if (id.includes('search_files') || id.includes('workspace.search')) return '🔎';
  if (id.includes('get_file_info')) return 'ℹ️';
  return '🔧';
}
function parseToolArgs(tc) {
  const argsRaw = tc?.function?.arguments;
  if (!argsRaw) return {};
  const parsed = typeof argsRaw === 'string' ? tryParseJSON(argsRaw) : argsRaw;
  return parsed || {};
}
function extractToolTarget(tc) {
  const name = tc?.function?.name || '';
  const args = parseToolArgs(tc);
  if (name === 'move_file') {
    const src = args.source || args.src || '';
    const dst = args.destination || args.dest || '';
    if (src && dst) return `${src} → ${dst}`;
    return src || dst || '—';
  }
  // 优化：workspace.search 显示查询内容
  if (name === 'workspace.search') {
    const q = args.query || '';
    const dir = args.directory || args.dir || '';
    if (q && dir) return `“${q}” in ${dir}`;
    return q || dir || '—';
  }
  if (name === 'write_file' || name === 'create_directory' || name === 'get_file_info' || name === 'search_files' || name === 'workspace.applyEdits' || name === 'workspace.search') {
    return args.path || args.directory || args.dir || args.pattern || '—';
  }
  return args.path || args.file || args.source || '—';
}
function getToolStatus(tc) {
  const callId = tc?.id;
  if (!callId) return 'running';
  const toolMsg = messages.value.find(m => m.role === 'tool' && m.tool_call_id === callId);
  if (!toolMsg) return 'running';
  const parsed = tryParseJSON(toolMsg.content);
  if (parsed && typeof parsed === 'object' && 'error' in parsed) return 'error';
  return 'success';
}

// 仅用于渲染：隐藏 tool 回合，避免打扰 UI
const visibleMessages = computed(() => messages.value.filter(m => m.role !== 'tool'));

// 在“我”的消息上提供复制与撤回
async function onCopyMessage(m) {
  try {
    const text = String(m?.content ?? '');
    if (navigator.clipboard && navigator.clipboard.writeText) {
      await navigator.clipboard.writeText(text);
    } else {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.position = 'fixed';
      ta.style.opacity = '0';
      document.body.appendChild(ta);
      ta.select();
      document.execCommand('copy');
      document.body.removeChild(ta);
    }
    copiedHintId.value = m?.id || null;
    setTimeout(() => { if (copiedHintId.value === (m?.id || null)) copiedHintId.value = null; }, 1200);
  } catch (err) {
    console.warn('Failed to copy message:', err);
  }
}

// 撤回消息
function onRetractMessage(m) {
  if (!m || !m.id) return;
  const idx = messages.value.findIndex(x => x && x.id === m.id);
  if (idx < 0) return;
  
  // 获取当前用户消息的内容，用于重新显示到输入框
  const messageContent = m.content || '';
  
  // 获取消息的时间戳，用于AI时间线回退
  const messageTimestamp = m.timestamp || Date.now();
  
  // 删除从当前用户消息开始的所有后续消息（包括当前消息）
  messages.value.splice(idx);
  
  // 触发持久化保存
  scheduleSaveMessages();
  
  // 将消息内容和时间戳发送到输入框
  emit('retract-to-input', messageContent, messageTimestamp);
  
  // 更新虚拟滚动
  nextTick(() => {
    forceVscrollUpdate();
  });
  
  // TODO: 这里应该调用回退变更操作的逻辑
  // 需要与AI时间线系统集成，回退从当前会话开始的所有文件变更
}

// 监听消息数量变化：仅在已在底部时自动滚底，避免打断用户查看历史
watch(() => visibleMessages.value.length, (now, old) => {
  if (now > old && isAtBottom.value) {
    nextTick(() => scrollToBottom());
  }
});

function append(role, content) {
  // 对助手消息做展示前规范化，并补充稳定 id 和时间戳
  const msg = { 
    id: uid('msg_'), 
    rt: 0, 
    role, 
    content: role === 'assistant' ? sanitizeAssistant(content) : content,
    timestamp: Date.now() // 添加时间戳用于回退功能
  };
  messages.value.push(msg);
  nextTick(() => {
    const el = scrollRef.value && scrollRef.value.$el ? scrollRef.value.$el : null;
    if (el) {
      el.scrollTop = el.scrollHeight;
    }
    forceVscrollUpdate();
  });
}

// 新增：滚动到底部与更新回调
function scrollToBottom() {
  const sc = scrollRef.value;
  const last = Math.max(0, visibleMessages.value.length - 1);
  if (sc && typeof sc.scrollToItem === 'function') {
    sc.scrollToItem(last);
  } else {
    const el = sc && sc.$el ? sc.$el : null;
    if (el) el.scrollTop = el.scrollHeight;
  }
}

function scrollToMessageById(id) {
  if (!id) return;
  const list = visibleMessages.value;
  const idx = list.findIndex(m => m.id === id);
  if (idx >= 0) {
    const sc = scrollRef.value;
    if (sc && typeof sc.scrollToItem === 'function') sc.scrollToItem(idx);
  }
}

function bumpMessageLayout(id) {
  const list = messages.value;
  const m = list.find(x => x && x.id === id);
  if (m) {
    m.rt = (typeof m.rt === 'number' ? m.rt + 1 : 1);
    forceVscrollUpdate();
  }
}

function onVScrollUpdate(startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
  const vmLen = visibleMessages.value.length;
  const last = Math.max(0, vmLen - 1);
  const vEnd = Math.max(-1, Math.min(Number.isFinite(visibleEndIndex) ? visibleEndIndex : -1, last));
  // 容差1条，避免边界抖动
  isAtBottom.value = vEnd >= (last - 1);
  // 持久化滚动位置（按会话）
  schedulePersistScroll(Math.max(0, vEnd));
}

function findLatestChatTaskId() {
  // 优先查找与当前 messages 对象和 modelKey 匹配的任务
  const all = [...aiTools.runningTasks, ...aiTools.queuedTasks];
  for (let i = all.length - 1; i >= 0; i--) {
    const t = all[i];
    if (t.toolId === 'chat.complete' && t.parameters?.messages === messages.value && t.parameters?.modelKey === props.modelKey) {
      return t.id;
    }
  }
  // 回退：取最近的 chat.complete 任务
  const latest = all.reverse().find(t => t.toolId === 'chat.complete');
  return latest?.id || null;
}

// 取消当前聊天请求（包括多轮工具调用）
function cancelCurrentRequest() {
  let targetId = currentTaskId.value;
  if (!targetId) targetId = findLatestChatTaskId();
  
  if (targetId) {
    const cancelled = aiTools.cancelExecution(targetId);
    if (cancelled) {
      error.value = "已取消发送";
      sending.value = false;
      currentTaskId.value = null;
      
      console.log('[ChatView] Cancelled chat request:', targetId);
    }
  }
  
  // 额外取消所有正在运行的聊天相关任务
  const runningChatTasks = aiTools.runningTasks.filter(task => 
    task.toolId === 'chat.complete' || 
    task.toolId.startsWith('workspace.') ||
    task.toolId.includes('_file') ||
    task.toolId.includes('search')
  );
  
  for (const task of runningChatTasks) {
    aiTools.cancelExecution(task.id);
    console.log('[ChatView] Cancelled related task:', task.id, task.toolId);
  }
  
  if (runningChatTasks.length > 0) {
    error.value = `已取消发送及 ${runningChatTasks.length} 个相关任务`;
  }
}

// old send removed

async function sendText(text) {
  if (!props.modelKey) {
    error.value = t ? t('chat.select_model_warning') : "请先在右侧配置并选择模型键";
    return;
  }
  if (!text || !text.trim()) return;
  append("user", text);
  error.value = "";
  sending.value = true;
  currentTaskId.value = null;
  
  // 将已注册工具（排除 chat.complete 本身）以 OpenAI 工具格式传给模型
  const tools = aiTools.registeredTools
    .filter(t => t.id !== 'chat.complete')
    .map(t => ({
      type: 'function',
      function: {
        name: t.id,
        description: t.description,
        parameters: t.parameters || undefined,
      }
    }));
  
  // 创建中断控制器
  const abortController = new AbortController();
  currentTaskId.value = `chat-${Date.now()}`;
  
  // 用于追踪当前 AI 回答消息
  let currentAssistantMessage = null;
  
  try {
    // 开始多轮工具调用对话
    console.log('[ChatView] Starting multi-round conversation');
    
    const execPromise = aiTools.executeTool({
      toolId: 'chat.complete',
      parameters: { 
        messages: messages.value, 
        modelKey: props.modelKey, 
        tools, 
        toolChoice: 'auto' 
      },
      metadata: { signal: abortController.signal }
    });

    // 下一帧查找新创建的任务并缓存其 id
    await nextTick();
    currentTaskId.value = findLatestChatTaskId();

    const res = await execPromise;
    if (res.success) {
      const data = res.data || {};

      // 使用增强的多轮工具调用编排器，带流式回调
      const orchestrated = await orchestrateToolCalls(
        { content: data.content, raw: data.raw },
        props.modelKey,
        messages.value,
        aiTools, // 添加缺失的 store 参数
        {
          maxRounds: props.multiRoundOptions.maxRounds || 100,
          enableMultiRound: props.multiRoundOptions.enabled !== false,
          abortSignal: abortController.signal,
          
          // 实时消息更新回调
          onMessageUpdate: (updatedMessages) => {
            messages.value = updatedMessages.map(m => {
              const msg = m && m.role === 'assistant' ? { ...m, content: sanitizeAssistant(m.content || '') } : { ...m };
              if (!msg.id) msg.id = uid('msg_');
              if (typeof msg.rt !== 'number') msg.rt = 0;
              return msg;
            });
            // 强制更新虚拟滚动并滚动到底部
            nextTick(() => {
              forceVscrollUpdate();
              scrollToBottom();
            });
          },
          
          // AI 响应实时显示回调
          onAssistantResponse: (response) => {
            if (response && response.trim()) {
              // 如果已经有当前助手消息，更新内容
              if (currentAssistantMessage) {
                currentAssistantMessage.content = sanitizeAssistant(response);
                currentAssistantMessage.rt = (currentAssistantMessage.rt || 0) + 1;
              } else {
                // 创建新的助手消息
                currentAssistantMessage = {
                  id: uid('msg_'),
                  role: 'assistant',
                  content: sanitizeAssistant(response),
                  rt: 0,
                  timestamp: Date.now()
                };
                messages.value.push(currentAssistantMessage);
              }
              // 强制更新虚拟滚动并滚动到底部
              nextTick(() => {
                forceVscrollUpdate();
                scrollToBottom();
              });
            }
          },
          
          // 工具调用开始回调
          onToolCallStart: (round, toolCalls) => {
            if (props.multiRoundOptions.showProgress) {
              console.log(`[ChatView] Starting round ${round} with ${toolCalls.length} tools`);
              // 可以在这里添加 UI 提示，比如显示“正在执行第 ${round} 轮工具...”
            }
          },
          
          // 轮次完成回调
          onRoundComplete: (round, toolResults) => {
            console.log(`[ChatView] Round ${round} completed with ${toolResults.length} tool results`);
            if (props.multiRoundOptions.showProgress) {
              // 可以在这里显示进度信息
            }
          }
        }
      );

      // 最终结果处理（如果没有工具调用）
      if (!orchestrated.hasToolCalls) {
        // 无工具调用，直接显示首次回答
        if (!currentAssistantMessage && data.content) {
          append("assistant", data.content);
        }
      }
      
      console.log(`[ChatView] Multi-round conversation completed with ${orchestrated.totalRounds || 0} rounds`);
      
    } else {
      if (res.errorType === 'cancelled') {
        error.value = '已取消发送';
      } else if (res.errorType === 'timeout') {
        error.value = '请求超时，请稍后重试';
      } else {
        error.value = res.error || '未知错误';
      }
    }
  } catch (e) {
    const msg = (typeof e === 'object' && e && 'message' in e) ? String((e)['message']) : String(e);
    if (/AbortError/i.test(msg) && /Timeout/i.test(msg)) {
      error.value = '请求超时，请稍后重试';
    } else if (/AbortError|aborted|cancel/i.test(msg)) {
      error.value = '已取消发送';
    } else {
      error.value = msg;
    }
  } finally {
    // 不论成功失败，都复位 sending
    sending.value = false;
    currentTaskId.value = null;
    
    // 最终更新虚拟滚动
    nextTick(() => {
      forceVscrollUpdate();
      scrollToBottom();
    });
  }
}

// 暴露消息与控制方法，父组件通过保持挂载来维持会话
defineExpose({
  sendText,
  sending: readonly(sending), // 使用 readonly 包装，确保外部能正确访问
  messages,
  cancelCurrentRequest,
  scrollToBottom,
  scrollToMessageById,
  forceVscrollUpdate,
  bumpMessageLayout,
});

// no presets; keep UI minimal
</script>

<template>
  <div class="flex h-full min-h-0 flex-col relative">
    <DynamicScroller
      ref="scrollRef"
      :items="visibleMessages"
      key-field="id"
      class="flex-1 min-h-0 overflow-auto p-2 bg-slate-50/60 dark:bg-neutral-900/40 dark:text-neutral-300 thin-scrollbar scrollbar-compact scrollbar-track"
      :min-item-size="vscrollMinItemSize"
      :buffer="vscrollBuffer"
      :prerender="vscrollPrerender"
      :emit-update="true"
      @update="onVScrollUpdate"
    >
      <template #default="{ item: m, index: i, active }">
        <DynamicScrollerItem :item="m" :active="active" :index="i" :size-dependencies="[m.content ? m.content.length : 0, (m.content ? m.content.split('\n').length : 0), (Array.isArray(m.tool_calls) ? m.tool_calls.length : 0), (typeof m.rt === 'number' ? m.rt : 0)]">
          <div class="mb-2">
            <div
              class="flex"
              :class="m.role === 'user' ? 'justify-end' : 'justify-start'"
            >
              <div class="flex flex-col"
                   :class="m.role === 'user' ? 'items-end' : 'items-start'">
                <div
                  class="flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-[10px] text-white"
                  :class="{
                    'bg-emerald-500': m.role === 'user',
                    'bg-indigo-500': m.role === 'assistant',
                    'bg-sky-500': m.role === 'system' || m.role === 'tool',
                  }"
                >
                  {{ m.role === 'user' ? (t ? t('chat.me') : '我') : (m.role === 'system' ? (t ? t('chat.system') : '系') : (m.role === 'tool' ? '🔧' : 'AI')) }}
                </div>

                <div v-if="m.role !== 'tool'" class="my-2 mx-1">
                  <div class="group flex items-start gap-2" :class="m.role === 'user' ? 'flex-row-reverse' : 'flex-row'">
                    <div
                      class="inline-block w-full max-w-full text-[13px] leading-6 align-top overflow-hidden"
                      :class="m.role === 'user'
                        ? 'bg-[#2A2B2E] text-white rounded-bl-lg rounded-tl-lg rounded-br-lg px-3 py-2'
                        : 'text-slate-900 dark:text-neutral-300'">
                      <div class="whitespace-pre-wrap break-words">{{ m.content }}</div>

                      <!-- 工具调用：响应式卡片布局，根据容器宽度自适应 -->
                      <div v-if="Array.isArray(m.tool_calls) && m.tool_calls.length"
                           :class="['grid gap-2 w-full', (m.content && String(m.content).trim()) ? 'mt-2' : 'mt-0']"
                           style="grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));">
                        <div v-for="(tc, idx) in m.tool_calls" :key="tc.id || idx"
                             class="flex items-center gap-2 rounded-md border border-slate-200 bg-white/90 px-2 py-1.5 text-[12px] shadow-sm dark:border-neutral-800 dark:bg-neutral-900/80 dark:text-neutral-300 min-w-0">
                          <div class="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-indigo-50 text-base dark:bg-neutral-800">
                            {{ getToolIcon(tc.function?.name) }}
                          </div>
                          <div class="min-w-0 flex-1 text-slate-700 dark:text-neutral-200">
                            <div class="truncate font-mono text-[12px] text-slate-700 dark:text-neutral-300" :title="extractToolTarget(tc)">{{ extractToolTarget(tc) }}</div>
                          </div>
                          <div class="shrink-0">
                            <span v-if="getToolStatus(tc) === 'running'" class="inline-flex items-center text-slate-400 dark:text-neutral-400">
                              <span class="mr-1 inline-block h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent"></span>
                              进行中
                            </span>
                            <span v-else-if="getToolStatus(tc) === 'success'" class="inline-flex items-center text-emerald-600 dark:text-emerald-400">✔ 成功</span>
                            <span v-else class="inline-flex items-center text-red-500">✖ 失败</span>
                          </div>
                        </div>
                      </div>
                    </div>

                    <!-- 气泡旁侧操作：仅对用户消息显示（单色图标，水平排列，防挤压） -->
                    <div v-if="m.role === 'user'" class="opacity-0 group-hover:opacity-100 transition-opacity duration-150 flex items-center gap-1 mt-1 select-none flex-none">
                      <button class="inline-flex items-center justify-center rounded p-1 text-slate-500 hover:text-emerald-600 dark:text-neutral-400 dark:hover:text-emerald-400 shrink-0"
                              title="复制"
                              @click.stop="onCopyMessage(m)">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
                          <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                          <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                        </svg>
                      </button>
                      <button class="inline-flex items-center justify-center rounded p-1 text-slate-500 hover:text-red-500 dark:text-neutral-400 dark:hover:text-red-400 shrink-0"
                              title="撤回"
                              @click.stop="onRetractMessage(m)">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
                          <path d="M9 14l-4-4 4-4"></path>
                          <path d="M20 20a8 8 0 0 0-8-8H5"></path>
                        </svg>
                      </button>
                      <span v-if="copiedHintId === m.id" class="text-[11px] text-emerald-500 dark:text-emerald-400">已复制</span>
                    </div>
                  </div>
                </div>

                <!-- 角色为 tool 的结果不再展示（用于状态判定） -->
              </div>
            </div>
          </div>
        </DynamicScrollerItem>
      </template>
    </DynamicScroller>

    <div v-if="sending || hasRunningTask" class="flex items-start gap-2 px-2">
      <div class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-violet-500 text-[11px] text-white">…</div>
      <div class="flex items-center gap-2 rounded-md border border-slate-200 bg-white px-3 py-2 text-slate-600 shadow-sm dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-400">
        <div class="flex items-center gap-1">
          <span class="h-2 w-2 animate-bounce rounded-full bg-current" style="animation-delay:0s"></span>
          <span class="h-2 w-2 animate-bounce rounded-full bg-current" style="animation-delay:.15s"></span>
          <span class="h-2 w-2 animate-bounce rounded-full bg-current" style="animation-delay:.3s"></span>
        </div>
        
      </div>
    </div>


    <slot />
    <div v-if="error" class="px-3 pb-2 text-xs text-red-500 dark:text-red-400">{{ error }}</div>
    
    <!-- 任务状态面板（开发调试用，可选） -->
    <div v-if="runningChatTasks.length > 0" class="px-3 pb-2 text-xs text-slate-500 dark:text-neutral-400">
      <div class="flex items-center gap-2">
        <span>运行中任务:</span>
        <div v-for="task in runningChatTasks" :key="task.id" class="flex items-center gap-1">
          <span class="inline-block w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
          <span>{{ task.toolId }}</span>
          
        </div>
      </div>
    </div>
  </div>
</template>





